---
id: cross-tree-table
title: 交叉树状表格
---

交叉树状表格（CrossTreeTable）与交叉表（CrossTable）类似，但在表格左侧提供了树状展开/收拢的能力，更适用于日常业务开发。

## 主要特性

- 不依赖特定组件库，可独立使用
- 简单、一致的 API 与渲染模型：**`左树 + 上树 => 表格`**
- 高性能：数据量较大时，自动开启虚拟滚动

## 用法

CrossTreeTable 主要由以下几个部分组成：

- `primaryColumn` 描述表格第一列
- 通过 `leftTree` 描述表格左侧的树状结构；
- 通过 `topTree` 描述表格上方的树状结构；
- 通过 `getValue` 来定义每个单元格内容；
- CrossTreeTable 会根据 leftTree/topTree 来渲染表格结构，并调用 `getValue` 获取单元格的内容

```js
// prettier-ignore
<CrossTreeTable
  // 推荐为 CrossTreeTable 设置一个默认列宽
  defaultColumnWidth={100}

  // 非受控用法
  defaultOpenKeys={[leftTree[0].key]}

  // 受控用法
  // openKeys={openKeys}
  // onChangeOpenKeys={nextOpenKeys => { /* update openKeys */ }}

  // 表格第一列的配置
  primaryColumn={{ lock: true, name: '数据维度', width: 200 }}

  leftTree={leftTree}
  topTree={topTree}
  getValue={(leftNode, topNode) => {
    // 自定义的取数逻辑，针对每个单元格都会调用一次
    // leftNode 表示当前单元格对应的左侧树节点，topNode 是对应的上方树节点
  }}

  // 可选的自定义的渲染逻辑
  render={(value, leftNode, topNode) => {
    return value
  }}
/>
```

```jsx live
function BasicCrossTreeTableUsage() {
  const leftTree = [
    {
      key: 'forenoon',
      value: '上午',
      data: { parent: true },
      children: [
        { key: '9', value: '9:00-10:00', data: { x: 0 } },
        { key: '10', value: '10:00-11:00', data: { x: 1 } },
        { key: '11', value: '11:00-12:00', data: { x: 2 } },
      ],
    },
    {
      key: 'afternoon',
      value: '下午',
      data: { parent: true },
      children: [
        { key: '14', value: '14:00-15:00', data: { x: 3 } },
        { key: '15', value: '15:00-16:00', data: { x: 4 } },
        { key: '16', value: '16:00-17:00', data: { x: 5 } },
      ],
    },
    {
      key: 'evening',
      value: '晚上',
      data: { parent: true },
      children: [
        { key: '20', value: '20:00-21:00', data: { x: 7 } },
        { key: '21', value: '21:00-22:00', data: { x: 8 } },
      ],
    },
  ]
  const makeTopChildren = (keyPrefix) => [
    { key: `${keyPrefix}-week-0`, value: '第一周', data: { y: 0 } },
    { key: `${keyPrefix}-week-1`, value: '第二周', data: { y: 1 } },
    { key: `${keyPrefix}-week-2`, value: '第三周', data: { y: 2 } },
    { key: `${keyPrefix}-week-3`, value: '第四周', data: { y: 3 } },
  ]
  const topTree = [
    { key: '2021-01', value: '2021-01', children: makeTopChildren('2021-01') },
    { key: '2021-02', value: '2021-02', children: makeTopChildren('2021-02') },
    { key: '2021-03', value: '2021-03', children: makeTopChildren('2021-03') },
    { key: '2021-04', value: '2021-04', children: makeTopChildren('2021-04') },
    { key: '2021-05', value: '2021-05', children: makeTopChildren('2021-05') },
    { key: '2021-06', value: '2021-06', children: makeTopChildren('2021-06') },
  ]
  const getValue = (leftNode, topNode) => {
    if (leftNode.data.parent) {
      const map = {
        forenoon: '万事开头难',
        afternoon: '然后中间难',
        evening: '最后结束难',
      }
      return map[leftNode.key]
    }
    const courses = ['数学', '英语', '计算机基础', '数据结构', '线性代数', '微积分', '概率论']
    const i = (leftNode.data.x + topNode.data.y) % courses.length
    return courses[i]
  }
  return (
    <div>
      <p style={{ fontSize: 16, marginTop: 0 }}>2021年 学习计划</p>
      <CrossTreeTable
        // 非受控用法：
        defaultOpenKeys={[leftTree[0].key]}
        // 受控用法：
        // const [openKeys, onChangeOpenKeys] = useState([leftTree[0].key])
        // openKeys={openKeys}
        // onChangeOpenKeys={onChangeOpenKeys}
        // 表格第一列的配置
        primaryColumn={{ lock: true, name: '数据维度', width: 200 }}
        defaultColumnWidth={100}
        leftTree={leftTree}
        topTree={topTree}
        getValue={getValue}
      />
    </div>
  )
}
```

## leftTree 的结构 / topTree 的结构

leftTree/topTree 都是一个具有 key/value/children 嵌套结构的数组，详见 [交叉表文档](cross-table).

注意 CrossTreeTable 对于 leftTree 和 topTree 的处理有所不同：

- leftTree 中的每个节点对应表格中的一行，包括叶子节点和非叶节点
- topTree 中的叶子节点对应 表格上的一列

## 其他 props

CrossTreeTable 的底层依赖了 `BaseTable`，故两者的 props 大部分是相同的。两者的不同点具体如下：

- CrossTreeTable 没有 dataSource 和 columns
  - 表格结构由 leftTree 和 rightTree 提供，而单元格内容由 getValue 提供
  - 单元格渲染内容可使用 render 进行自定义；单元格的 props（即表格内的 td 元素）可使用 getCellProps 进行自定义
- CrossTreeTable 没有 primaryKey
  - CrossTreeTable 左侧树中每个节点都有一个唯一的 key 值，故不再需要上层指定 primaryKey
- 其他新增的 props
  - CrossTreeTable 使用 primaryColumn 来描述 表格第一列的配置
  - openKeys/onChangeOpenKeys/defaultOpenKeys：树状模式下展开节点的 key 数组

CrossTreeTable props 具体如下：

```typescript
export interface CrossTreeTableProps extends Omit<BaseTableProps, 'dataSource' | 'columns' | 'primaryKey'> {
  primaryColumn: CrossTableLeftMetaColumn
  leftTree: LeftCrossTreeNode[]
  topTree: TopCrossTreeNode[]

  defaultOpenKeys?: string[]
  openKeys?: string[]
  onChangeOpenKeys?(nextOpenKeys: string[]): void

  getValue(leftNode: LeftCrossTreeNode, topNode: TopCrossTreeNode, leftDepth: number, topDepth: number): any
  render?(
    value: any,
    leftNode: LeftCrossTreeNode
    topNode: TopCrossTreeNode,
    leftDepth: number,
    topDepth: number,
  ): ReactNode
  getCellProps?(
    value: any,
    leftNode: LeftCrossTreeNode,
    topNode: TopCrossTreeNode,
    leftDepth: number,
    topDepth: number,
  ): CellProps
}

export interface CrossTableLeftMetaColumn extends Omit<ArtColumnStaticPart, 'hidden'> {
  /** 自定义渲染方法 */
  render?(leftNode: LeftCrossTreeNode, leftDepth: number): ReactNode

  /** 自定义的获取单元格 props 的方法 */
  getCellProps?(leftNode: LeftCrossTreeNode, leftDepth: number): CellProps
}
```

不要被长长的 TypeScript 类型代码吓到，CrossTreeTable 的 props 其实和 BaseTable 差别不大。

## 交叉树状表格定制

CrossTreeTable 的底层仍是 `<BaseTable />`。

对 CrossTreeTable 进行定制时，可以先通过 `buildCrossTreeTable` 根据 CrossTreeTable 的输入生成 dataSource / columns，进行一些自定义的处理，然后再将处理好的数据传给 BaseTable，并注意为 BaseTable 设置 `primaryKey={ROW_KEY}`。

可以使用 [pipeline](/docs/pipeline/) 进行表格功能拓展（具体可以参考这个[示例](/examples/others/cross-table-customization)），但因为 CrossTreeTable 本身很复杂，只有一部分比较简单的 pipeline feature 可以和 CrossTreeTable 一起工作.

```jsx
import { BaseTable } from 'ali-react-table'
import { buildCrossTreeTable, ROW_KEY } from 'ali-react-table/pivot'

function MyComplexTable() {
  // 将「传给 CrossTreeTable 的参数」传给 buildCrossTreeTable
  const { dataSource, columns } = buildCrossTreeTable({
    leftTree,
    topTree,
    getValue,
    render,

    // 调用 buildCrossTreeTable 必须传入一份受控的 openKeys 状态
    openKeys: ['a', 'b', 'c'],
    onChangeOpenKeys(nextOpenKeys) {},

    // 这里也支持 CrossTreeTable 的其他 props
  })

  // 在这里可以对 dataSource 和 columns 进行一定的处理后，再将数据传递给 BaseTable

  return (
    <BaseTable
      className="my-complex-table"
      primaryKey={ROW_KEY}
      defaultColumnWidth={100}
      dataSource={dataSource}
      columns={columns}
    />
  )
}
```
